/*
 * standardrequestmanager.cpp
 *
 *  Created on: Sep 11, 2011
 *      Author: anonovermind <anonovermind@gmail.com>
 */

#include <boost/bind.hpp>
#include <algorithm>
#include <fstream>
#include "../include/standardrequestmanager.h"
#include "exceptions.h"

namespace Wishmaster2
{
namespace Network
{

StandardRequestManager::StandardRequestManager() : m_currentQueueMaxSize(10), m_currentId(0)
{
}

StandardRequestManager::~StandardRequestManager()
{
	std::for_each(m_media.begin(), m_media.end(), [](NetworkMedium* medium) ->  void { delete medium; });
}

void StandardRequestManager::registerMedium(NetworkMedium* medium)
{
	m_media.push_back(medium);
}

void StandardRequestManager::unregisterMedium(const std::string& protocol)
{
	auto it = std::find_if(m_media.begin(), m_media.end(),
			[&protocol](NetworkMedium* media) -> bool { return media->protocol() == protocol; });
	if(it == m_media.end())
		throw Exception(std::string("Protocol is not registered: ") + protocol);
	delete (*it);
	m_media.erase(it);
}

RequestPtr StandardRequestManager::createRequest(const Url& url)
{
	std::string protocol = url.scheme();
	if(protocol.empty())
		throw InvalidUrlException(std::string("Protocol is not specified"));
	auto it = std::find_if(m_media.begin(), m_media.end(),
			[&protocol](NetworkMedium* media) -> bool { return media->protocol() == protocol; });
	if(it == m_media.end())
		throw InvalidUrlException(std::string("Unknown protocol: ") + protocol);
	return (*it)->createRequest(m_currentId++, url);
}

bool StandardRequestManager::enqueueRequest(const RequestPtr& request)
{
	request->connectSignalFinished(boost::bind(&StandardRequestManager::requestFinished, this, _1));
	request->connectSignalProgress(boost::bind(&StandardRequestManager::requestProgress, this, _1, _2, _3));
	request->connectSignalError(boost::bind(&StandardRequestManager::requestError, this, _1, _2));

	if(m_current.size() < m_currentQueueMaxSize)
	{
		m_current.push_back(request);
		request->perform();
	}
	else
	{
		m_pending.push_back(request);
	}
	removeStaleDoneRequests();
	return true;
}

void StandardRequestManager::acknowledgeRequest(int id)
{
	m_done.remove_if([id](const DoneRequest& entry) -> bool { return entry.request->id() == id; } );
}

void StandardRequestManager::setQueueDepth(int depth)
{
	m_currentQueueMaxSize = depth;
}

RequestPtr StandardRequestManager::request(int id) const
{
	auto doneIterator = std::find_if(m_done.begin(), m_done.end(),
			[id](const DoneRequest& entry) -> bool { return entry.request->id() == id; });
	if(doneIterator != m_done.end())
		return (*doneIterator).request;
	auto pendingIterator = std::find_if(m_pending.begin(), m_pending.end(),
			[id](const RequestPtr& req) -> bool { return req->id() == id; });
	if(pendingIterator != m_pending.end())
		return (*pendingIterator);

	auto currentIterator = std::find_if(m_current.begin(), m_current.end(),
				[id](const RequestPtr& req) -> bool { return req->id() == id; });
		if(currentIterator != m_current.end())
			return (*currentIterator);
	return RequestPtr();
}

void StandardRequestManager::saveSettings(const std::string& path)
{
	for(NetworkMedium* m : m_media)
	{
		std::string protocol = m->protocol();
		std::string filename = path + '/' + protocol + ".context";
		std::fstream stream(filename, std::ios_base::out | std::ios_base::trunc);
		m->context()->serialize(stream);
	}
}

void StandardRequestManager::loadSettings(const std::string& path)
{
	for(NetworkMedium* m : m_media)
	{
		std::string protocol = m->protocol();
		std::string filename = path + '/' + protocol + ".context";
		std::fstream stream(filename, std::ios_base::in);
		m->context()->deserialize(stream);
	}
}

void StandardRequestManager::requestFinished(int id)
{
	moveFinishedRequestToDone(id);
	enqueueNextRequest();
}

void StandardRequestManager::requestProgress(int id, int current, int total)
{

}

void StandardRequestManager::requestError(int id, const std::string message)
{
	moveFinishedRequestToDone(id);
	enqueueNextRequest();
}

void StandardRequestManager::moveFinishedRequestToDone(int id)
{
	auto it = std::find_if(m_current.begin(), m_current.end(),
			[id](const RequestPtr& request) -> bool { return request->id() == id; });
	if(it == m_current.end())
		return;
	RequestPtr request = *it;
	m_current.erase(it);
	m_done.push_back(DoneRequest{ std::chrono::system_clock::now(), request });
}

void StandardRequestManager::enqueueNextRequest()
{
	if(m_pending.empty())
		return;
	RequestPtr request = m_pending.front();
	m_pending.pop_front();

	m_current.push_back(request);
	request->perform();
}

void StandardRequestManager::removeStaleDoneRequests()
{
	std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
	m_done.remove_if([&now](const DoneRequest& entry) -> bool { return now - entry.doneTime > std::chrono::seconds(30); } ); // FIXME hardcoded
}

} /* namespace Network */
} /* namespace Wishmaster2 */
